#include <linux/module.h>
#include <linux/poll.h>

#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include <linux/gpio/consumer.h>
#include <linux/platform_device.h>
#include <linux/of_gpio.h>
#include <linux/of_irq.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/slab.h>
#include <linux/fcntl.h>

struct gpio_key{
    int gpio;
    struct gpio_desc *gpiod;
    int flag;
    int irq;
};


static int major = 0;
static struct class *gpio_key_class;
static struct gpio_key *gpio_keys_imx6;


#define BUF_LEN 128
static int g_keys[BUF_LEN];
static int r, w;
struct fasync_struct *button_fasync;

#define NEXT_POS(x)  ((x+1) % BUF_LEN)

static int is_key_buf_empty(void)
{
    return (r ==w);
}

static int is_key_buf_full(void)
{
    return (r == NEXT_POS(w));
}

static void put_key(int key)
{
    if(!is_key_buf_full())
    {
        g_keys[w] = key;
        w = NEXT_POS(w);
    }
}

static int get_key(void)
{
    int key = 0;
    if(!is_key_buf_empty())
    {
        key = g_keys[r];
        r = NEXT_POS(r);
    }
    return key;
}


static DECLARE_WAIT_QUEUE_HEAD(gpio_key_wait);

static ssize_t gpio_key_drv_read(struct file *file, char __user *buf, size_t size, loff_t *offset)
{
    int err;
	int key;


    wait_event_interruptible(gpio_key_wait, !is_key_buf_empty());
    key = get_key();
    err = copy_to_user(buf, &key, 4);

    return 4;
}

static unsigned int gpio_key_drv_poll(struct file *fp, poll_table *wait)
{

		printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
		poll_wait(fp,&gpio_key_wait, wait);	
		return is_key_buf_empty() ? 0 : POLLIN | POLLRDNORM;
}

static int gpio_key_drv_fasync(int fd, struct file *file, int on)
{
	if (fasync_helper(fd, file, on, &button_fasync) >= 0)
		return 0;
	else
		return -EIO;
}
static struct file_operations gpio_key_drv = {
    .owner      = THIS_MODULE,
    .read       = gpio_key_drv_read,
	.poll       = gpio_key_drv_poll,
	.fasync     = gpio_key_drv_fasync,
};


static irqreturn_t gpio_key_isr(int irq, void *dev_id)
{
    struct gpio_key *gpio_key = dev_id;
    int val;
    int key;

    val = gpiod_get_value(gpio_key->gpiod);

    printk("key %d %d\n", gpio_key->gpio, val);

    key = (gpio_key->gpio << 8) | val;
    put_key(key);
    wake_up_interruptible(&gpio_key_wait);
	kill_fasync(&button_fasync, SIGIO, POLL_IN);

    return IRQ_HANDLED;
}

static int gpio_key_probe(struct platform_device *pdev)
{
    int err;
    struct device_node *node = pdev->dev.of_node;
    int count;
    int i;
    enum of_gpio_flags flag;
    printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);

    count = of_gpio_count(node);
    if(!count){
    printk("%s %s line %d, there is not ang gpio available\n", __FILE__, __FUNCTION__, __LINE__);
        return -1;
    }

    gpio_keys_imx6 = kzalloc(sizeof(struct gpio_key) * count, GFP_KERNEL);
    for (i = 0; i < count; i++) {
        gpio_keys_imx6[i].gpio = of_get_gpio_flags(node, i, &flag);
        if(gpio_keys_imx6[i].gpio < 0){
            printk("%s %s line %d, of_get_gpio_flags fail\n", __FILE__, __FUNCTION__, __LINE__);
            return -1;
        }
        gpio_keys_imx6[i].gpiod = gpio_to_desc(gpio_keys_imx6[i].gpio);
        gpio_keys_imx6[i].flag = flag & OF_GPIO_ACTIVE_LOW;
        gpio_keys_imx6[i].irq = gpio_to_irq(gpio_keys_imx6[i].gpio);
    }

    for(i = 0; i < count; i++){
        err = request_irq(gpio_keys_imx6[i].irq, gpio_key_isr, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING ,"imx6_gpio_key", &gpio_keys_imx6[i]);
    }

    major = register_chrdev(0, "imx6_gpio_key", &gpio_key_drv);

    gpio_key_class = class_create(THIS_MODULE, "imx6_gpio_key_class");
    if(IS_ERR(gpio_key_class)){
        printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
        unregister_chrdev(major, "imx6_gpio_key");
        return PTR_ERR(gpio_key_class);
    }

    device_create(gpio_key_class, NULL, MKDEV(major, 0), NULL, "imx6_gpio_key");

    return 0;

}

static int gpio_key_remove(struct platform_device *pdev)
{
	struct device_node *node = pdev->dev.of_node;
    int count;
    int i;
    
    
    device_destroy(gpio_key_class, MKDEV(major, 0));
    class_destroy(gpio_key_class);
    unregister_chrdev(major, "imx6_gpio_key");

    count = of_gpio_count(node);
    for(i = 0; i < count; i++){
        free_irq(gpio_keys_imx6[i].irq, &gpio_keys_imx6[i]);
    }
    kfree(gpio_keys_imx6);
    return 0;
}

static const struct of_device_id imx6s[] = {
    { .compatible = "imx6,gpio_key" },
    { },
};

static struct platform_driver gpio_keys_driver = {
    .probe  = gpio_key_probe,
    .remove = gpio_key_remove,
    .driver = {
        .name = "imx6_gpio_key",
        .of_match_table = imx6s,
    },
};

static int __init gpio_key_init(void)
{
    int err;
    printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
    err = platform_driver_register(&gpio_keys_driver);
    return err;
}

static void __exit gpio_key_exit(void)
{
    printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);

    platform_driver_unregister(&gpio_keys_driver);

}

module_init(gpio_key_init);
module_exit(gpio_key_exit);

MODULE_LICENSE("GPL");
